#include "Model/Package.hpp"
#include "Model/Project.hpp"
#include "Model/Requirement.hpp"
#include "Model/DuplicateNameError.hpp"
#include "Model/InvalidNameError.hpp"
#include "Model/InvalidHierarchyOperationError.hpp"
#include <boost/test/auto_unit_test.hpp>

namespace
{

using namespace Model;

BOOST_AUTO_TEST_CASE( testConstruction)
{
	const NameString name( RQ_STR( "Package Name"))
		, description( RQ_STR( "Package Description"));
	Package package( name, description);
	BOOST_CHECK( name == package.name());
	BOOST_CHECK( description == package.description());
	BOOST_CHECK( package.childPackagesBegin() == package.childPackagesEnd());
	BOOST_CHECK( NULL == package.project());
	BOOST_CHECK_EQUAL( 0, package.numberOfRequirements());

	BOOST_CHECK_THROW( Package( ""), Model::InvalidNameError);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testAddPackage)
{
	const NameString parentName( RQ_STR( "Parent Package"));
	const NameString childName( RQ_STR( "Child Package"));

	std::auto_ptr<Package> parent( new Package( parentName));

	BOOST_CHECKPOINT( "Test adding new child by name");
	Package* pChildPackage = parent->addNewPackage( childName);
	BOOST_CHECK_EQUAL( pChildPackage, *parent->childPackagesBegin());

	BOOST_CHECKPOINT( "Test adding duplicate child name by name");
	BOOST_CHECK_THROW( parent->addNewPackage( childName), DuplicateNameError);

	BOOST_CHECKPOINT( "Test adding package with empty name");
	BOOST_CHECK_THROW( parent->addNewPackage( ""), InvalidNameError);

	BOOST_CHECKPOINT( "Test adding a child that was root before");
	pChildPackage = new Package( RQ_STR( "New child package"));
	parent->addNewPackage( pChildPackage);
	BOOST_CHECK_EQUAL( pChildPackage, *(++parent->childPackagesBegin()));
	BOOST_CHECK_EQUAL( parent.get(), pChildPackage->parentPackage());

	BOOST_CHECKPOINT( "Test duplicate name when adding child package by object");
	std::auto_ptr<Package> pDupChildPackage( new Package( pChildPackage->name()));
	BOOST_CHECK_THROW( parent->addNewPackage( pDupChildPackage.get()), DuplicateNameError);

	BOOST_CHECKPOINT( "Test adding already existing child");
	parent->addNewPackage( pChildPackage);

	BOOST_CHECKPOINT( "Test moving child from one parent to another");
	std::auto_ptr< Package> secondParent( new Package( RQ_STR( "Second parent")));
	secondParent->addNewPackage( pChildPackage);
	BOOST_CHECK( ++parent->childPackagesBegin() == parent->childPackagesEnd());
	BOOST_CHECK_EQUAL( secondParent.get(), pChildPackage->parentPackage());

	BOOST_CHECKPOINT( "Test moving parent package to child");
	BOOST_CHECK_THROW( pChildPackage->addNewPackage( secondParent.get()), InvalidHierarchyOperationError);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testChildUsesName)
{
	const NameString childName( RQ_STR( "Child"));
	std::auto_ptr< Package> parent( new Package( RQ_STR( "Name")));
	parent->addNewPackage( childName);

	BOOST_CHECK_EQUAL( true, parent->childUsesName( childName));
	BOOST_CHECK_EQUAL( false, parent->childUsesName( RQ_STR( "Inexistent")));
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testFullID)
{
	const NameString childName( RQ_STR( "Child"));
	const NameString parentName( RQ_STR( "Parent"));
	std::auto_ptr<Package> parent( new Package( parentName));
	Package* child = parent->addNewPackage( childName);

	BOOST_CHECK( parentName == parent->fullID());
	BOOST_CHECK( parentName + RQ_STR( ".") + childName == child->fullID());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testRemoveChildPackage)
{
	const NameString childName( RQ_STR( "Child"));

	std::auto_ptr< Package> parent( new Package( RQ_STR( "Name")));
	Package* pChild = parent->addNewPackage( childName);

	parent->removeChildPackage( pChild);
	BOOST_CHECK( NULL == pChild->parentPackage());
	BOOST_CHECK( parent->childPackagesBegin() == parent->childPackagesEnd());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testAddingRemovedChildPackage)
{
	const NameString parentName( RQ_STR( "Parent Package"));
	const NameString childName( RQ_STR( "Child Package"));
	std::auto_ptr<Package> parent( new Package( parentName));
	Package* pChildPackage = parent->addNewPackage( childName);
	parent->removeChildPackage( pChildPackage);
	parent->addNewPackage( pChildPackage);
	BOOST_CHECK_EQUAL( parent.get(), pChildPackage->parentPackage());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSettingProject)
{
	// Testing setting project to a package with no project bound.
	const NameString parentName( RQ_STR( "Parent Package"));
	const NameString childName( RQ_STR( "Child Package"));
	const NameString grandChildName( RQ_STR( "Grand child package"));

	std::auto_ptr< Project> project( 
		new Project( "Project Name", "Project Description"));
	std::auto_ptr<Package> parent( new Package( parentName));
	Package* pChildPackage = parent->addNewPackage( childName);
	Package* pGrandChildPackage = pChildPackage->addNewPackage( grandChildName);
	parent->setProject( project.get());
	BOOST_CHECK_EQUAL( project.get(), pChildPackage->project());
	BOOST_CHECK_EQUAL( project.get(), pGrandChildPackage->project());

	// Testing setting NULL project to a package with a project bound.
	parent->setProject( NULL);
	BOOST_CHECK_EQUAL( static_cast< Project*>( NULL), pChildPackage->project());
	BOOST_CHECK_EQUAL( static_cast< Project*>( NULL), pGrandChildPackage->project());

	// Testing setting project for removed child packages.
	// Remove the child package first.
	pChildPackage->removeChildPackage( pGrandChildPackage);
	// Set the project
	parent->setProject( project.get());
	// Add again the child package
	pChildPackage->addNewPackage( pGrandChildPackage);
	BOOST_CHECK_EQUAL( project.get(), pGrandChildPackage->project());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testAddingPackageWhenProjectIsNotNull)
{
	const NameString parentName( RQ_STR( "Parent Package"));
	const NameString childName( RQ_STR( "Child Package"));

	// Testing adding new package when Project != NULL;
	std::auto_ptr< Project> project( new Project( "Project Name", "Project Description"));
	std::auto_ptr<Package> parent( new Package( parentName));
	parent->setProject( project.get());

	Package* pChildPackage = parent->addNewPackage( childName);
	BOOST_CHECK_EQUAL( pChildPackage, *parent->childPackagesBegin());
	BOOST_CHECK_EQUAL( project.get(), pChildPackage->project());


	BOOST_CHECKPOINT( "Test adding a child that was root before");
	pChildPackage = new Package( RQ_STR( "New child package"));
	parent->addNewPackage( pChildPackage);
	BOOST_CHECK_EQUAL( pChildPackage, *(++parent->childPackagesBegin()));
	BOOST_CHECK_EQUAL( parent.get(), pChildPackage->parentPackage());
	BOOST_CHECK_EQUAL( project.get(), pChildPackage->project());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testSettingPackageName)
{
	const NameString initialName( RQ_STR( "Initial"));
	const NameString changedName( RQ_STR( "Changed"));
	const NameString childName( RQ_STR( "Child"));
	Package package( initialName);
	
	BOOST_CHECKPOINT( "Happy flow");
	package.setName( changedName);
	BOOST_CHECK_EQUAL( changedName, package.name());

	BOOST_CHECKPOINT( "Setting the same name twice");
	package.setName( changedName);

	BOOST_CHECKPOINT( "Setting to empty name");
	BOOST_CHECK_THROW( package.setName( ""), InvalidNameError);

	BOOST_CHECKPOINT( "Setting name as a duplicate sibling");
	Package* pChild = package.addNewPackage( childName);
	Package* pChildToBeChanged = package.addNewPackage( "Child to be changed");
	BOOST_CHECK_THROW( pChildToBeChanged->setName( childName), DuplicateNameError);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testRequirementInsertion)
{
	Package package( "Package");
	Requirement* rootRq = new Requirement( "ROOT");
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq->addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq->addChildRequirement( "R2", 2);

	package.addRequirement( rootRq);
	BOOST_CHECK_EQUAL( 1, package.numberOfRequirements());
	BOOST_CHECK_EQUAL( rootRq, *package.requirementsBegin());
	BOOST_CHECK_EQUAL( &package, rootRq->package());
	BOOST_CHECK_EQUAL( &package, r1->package());
	BOOST_CHECK_EQUAL( &package, r11->package());
	BOOST_CHECK_EQUAL( &package, r12->package());
	BOOST_CHECK_EQUAL( &package, r2->package());

	BOOST_CHECKPOINT( "Testing adding duplicate requirement name");
	std::auto_ptr< Requirement> dupRoot( new Requirement( "ROOT"));
	BOOST_CHECK_THROW( package.addRequirement( dupRoot.get()), DuplicateNameError);

	BOOST_CHECKPOINT( "Testing adding previously removed root requirement");
	package.removeRequirement( rootRq);
	package.addRequirement( rootRq);
	BOOST_CHECK_EQUAL( 1, package.numberOfRequirements());
	BOOST_CHECK_EQUAL( rootRq, *package.requirementsBegin());
	BOOST_CHECK_EQUAL( &package, rootRq->package());
	BOOST_CHECK_EQUAL( &package, r1->package());
	BOOST_CHECK_EQUAL( &package, r11->package());
	BOOST_CHECK_EQUAL( &package, r12->package());
	BOOST_CHECK_EQUAL( &package, r2->package());

}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testEmptyRequirementInsertion)
{
	Package package( "Package");
	Requirement* rootRq = package.addRequirement( "ROOT", "Description");
	BOOST_CHECK_EQUAL( 1, package.numberOfRequirements());
	BOOST_CHECK_EQUAL( rootRq, *package.requirementsBegin());
	BOOST_CHECK_EQUAL( &package, rootRq->package());
	BOOST_CHECK_EQUAL( "ROOT", rootRq->name());
	BOOST_CHECK_EQUAL( "Description", rootRq->statement());

	BOOST_CHECKPOINT( "Adding duplicate name");
	BOOST_CHECK_THROW( package.addRequirement( "ROOT"), DuplicateNameError);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testRemovingRequirement)
{
	Package package( "Package");
	Requirement* rootRq = new Requirement( "ROOT");
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq->addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq->addChildRequirement( "R2", 2);

	package.addRequirement( rootRq);

	BOOST_CHECKPOINT( "Removing root requirement");
	package.removeRequirement( rootRq);
	BOOST_CHECK_EQUAL( 0, package.numberOfRequirements());
}

} // namespace
